" << Navigate
let g:cc_navigate_result_files = []
command! -nargs=* Navigate :call CCNavigate('<args>') 
nmap <leader>nn :call CCOpenNavigateSelector()<CR>
" >> Navigate

nmap <leader>tt :call CCTabSelector()<CR>
let g:cc_tab_selector_last_idx = -1
nmap <leader>l :call CCBufferSelector()<CR>
let g:cc_buffer_selector_last_idx = -1

let g:cc_snippet_actions = {
      \ '1. Introduce local variable': { -> CCIntroduceLocalVariable()},
      \ '2. Iterate over': { -> CCIterateOver()},
      \ '3. Fill method parameters': { -> CCFillMethodParameters()},
      \ '4. Fill method parameters(only variables)': { -> CCFillMethodParametersOnlyVar()}
      \ }
let g:cc_snippet_action_names = sort(keys(g:cc_snippet_actions))
let g:cc_snippet_auto_organize_imports = 0

autocmd FileType java nmap <leader>[ :tabp<CR>
autocmd FileType java nmap <leader>] :tabn<CR>

autocmd FileType java nmap <leader>b :call CCGotoImplemention()<CR>
autocmd FileType java nmap <leader>tb :call CCGotoImplementionTabPage()<CR>
autocmd FileType java nmap <leader>d :call CCGotoDefinition()<CR>
autocmd FileType java nmap <leader>td :call CCGotoDefinitionTabPage()<CR>
autocmd FileType java nmap <leader>c :call CCGotoReferences()<CR>
autocmd FileType java nmap <leader>r <Plug>(coc-rename)
"autocmd FileType java nmap <leader>s <Plug>(coc-range-select)
autocmd FileType java nmap <leader>s :call CCEchoSignature()<CR>
autocmd FileType java nmap <leader>wn <Plug>(coc-diagnostic-next)
autocmd FileType java nmap <leader>wp <Plug>(coc-diagnostic-prev)
autocmd FileType java nmap <leader>en <Plug>(coc-diagnostic-next-error)
autocmd FileType java nmap <leader>ep <Plug>(coc-diagnostic-prev-error)
autocmd FileType java nmap <leader>f :CocSearch <C-R>=expand("<cword>")<CR><CR>
autocmd FileType java xmap <leader>f :call CCSearchSelected()<CR>
autocmd FileType java xmap <leader>p <Plug>(coc-format-selected)
autocmd FileType java nmap <leader>1 :CocFix<CR>
autocmd FileType java nmap <leader>2 :call CCShowSnippetActions()<CR>
autocmd FileType java nmap <leader>i :CocAction<CR>
autocmd FileType java nmap <leader>o :call CCOrganizeImports()<CR>
autocmd FileType java nmap <leader>h :call CCHighlightSymbol()<CR>
"autocmd FileType java autocmd CursorHold * silent call CocActionAsync('highlight')
autocmd FileType java nmap <leader>m :call CCGotoCurrentMethodDefinition()<CR>
autocmd FileType java nmap <silent> K :call <SID>show_documentation()<CR>
autocmd FileType java nnoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? coc#float#scroll(1) : "\<C-f>"
autocmd FileType java nnoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? coc#float#scroll(0) : "\<C-b>"
autocmd FileType java command! -nargs=0 JFormat :call CocAction('format')
autocmd FileType java command! -nargs=0 JOutline :CocOutline
autocmd FileType java command! -nargs=0 JReference :call CCEchoJavaReference()
autocmd FileType java command! -nargs=0 JBreakpoint :call CCEchoJavaMethodBreakpoint()
autocmd FileType java command! -nargs=0 JInitialize :call CCInitJavaFile() 
autocmd FileType java command! -nargs=0 JCleanWorkspace :call CCCleanWorkspace() 
autocmd FileType java command! -nargs=0 JOpenServerLog :call CCOpenServerLog() 
autocmd FileType java command! -nargs=0 JDebugger :call CCOpenDebugger() 
autocmd FileType java command! -nargs=0 JOpenVerboseServerLog :call CCOpenVerboseServerLog() 
autocmd FileType java command! -nargs=* JCreateClass :call CCCreateJavaClass('<args>')
" Searh Examples
" 1. File extension filter
"    JSearch spring -g *.yml
" 2. Fuzzy
"    JSearch rest.*controller
autocmd FileType java command! -nargs=* JSearch :call CCSearchSmart('<args>') 
autocmd FileType java let b:coc_root_patterns=['pom.xml']

autocmd FileType java set expandtab
autocmd FileType java set shiftwidth=4
autocmd FileType java set tabstop=4
autocmd FileType java set cursorline
autocmd FileType java set nu
autocmd FileType java set ignorecase
autocmd FileType java set nobackup
autocmd FileType java set noswapfile
autocmd FileType java set linebreak
"autocmd FileType java set cc=120

"autocmd FileType java set statusline^=%{coc#status()}%{get(b:,'coc_current_function','')}
":verbose set statusline?
"autocmd FileType java set cmdheight=2

autocmd FileType java hi JEchoMsg ctermfg=DarkMagenta
autocmd FileType java hi JEchoError ctermfg=DarkRed
autocmd FileType java hi def link yamlKeyValueDelimiter Keyword
autocmd FileType java hi javaAnnotation ctermfg=136
"autocmd FileType java set tabline=%!CCIndexedTabLine()
autocmd Syntax java call CCJavaAdditionalHighlight()


" << coc functions
function! s:show_documentation()
  if CCCheckJavaUsage('') ==# 0
    return
  endif
  if (index(['vim','help'], &filetype) >= 0)
    execute 'h '.expand('<cword>')
  elseif (coc#rpc#ready())
    call CocActionAsync('doHover')
  else
    execute '!' . &keywordprg . " " . expand('<cword>')
  endif
endfunction

function! GetVisualSelection()
  let [line_start, column_start] = getpos("'<")[1:2]
  let [line_end, column_end] = getpos("'>")[1:2]
  let lines = getline(line_start, line_end)
  if len(lines) == 0
    return ''
  endif
  let lines[-1] = lines[-1][: column_end - (&selection == 'inclusive' ? 1 : 2)]
  let lines[0] = lines[0][column_start - 1:]
  return join(lines, "\n")
endfunction

function! CCSearchSelected()
  let sel = GetVisualSelection()
  let sel_regx = substitute(sel, ' ', '\\s', 'g')
  execute 'CocSearch '.sel_regx
endfunction

function! CCSearchSmart(arg)
  let keywords = a:arg
  let lowerkeywords = tolower(keywords)
  let replaced_keywords = substitute(keywords, ' ', '\\s', 'g')
  if lowerkeywords ==# keywords
    execute 'CocSearch -S '.replaced_keywords
  else
    execute 'CocSearch '.replaced_keywords
  endif
endfunction

function! CCEchoJavaReference()
  if CCCheckJavaUsage('Echo java reference') ==# 0
    return
  endif
  let hovers = CocAction('getHover')
  if len(hovers) == 0
    echohl JEchoError | echo "Error: Can't get hover" | echohl None
    return
  endif
  let signature = trim(get(hovers, 0))
  let comma_begin = match(signature, '(')
  if comma_begin < 0
    if match(signature, ' ') > 0
      " Variable in current class
      return
    endif
    let refs = signature
  else
    let method_name_rettype = split(strpart(signature, 0, comma_begin), ' ')
    let method_name = get(method_name_rettype, len(method_name_rettype) - 1)
    let method_name_parts = split(method_name, '\.')
    let onlyname = get(method_name_parts, len(method_name_parts) - 1)
    let decorated_method_name = strpart(method_name, 0, len(method_name) - len(onlyname) - 1).'#'.onlyname
    let refs = decorated_method_name
  endif
  if refs ==# ''
    return
  endif
  echohl JEchoMsg | echo refs | echohl None
endfunction

function! CCEchoJavaMethodBreakpoint()
  if CCCheckJavaUsage('Echo java method breakpoint') ==# 0
    return
  endif
  let ocol = col('.')
  let oline = line('.')
  call cursor(1, 1)
  let pkgline = getline(search('package.*;', 'cn'))
  call cursor(oline, ocol)
  if match(pkgline, '"') >= 0
    return
  endif
  let pkgbegin = matchend(pkgline, 'package ')
  let pkgend = match(pkgline, ';')
  let pkg = strpart(pkgline, pkgbegin, pkgend - pkgbegin)

  let clsidx = search('public class ', 'n')
  if clsidx ==# 0
    let clsidx = search('class ', 'n')
    let clsline = getline(clsidx)
    let clsbegin = matchend(clsline, 'class')
  else
    let clsline = getline(clsidx)
    let clsbegin = matchend(clsline, 'public class')
  endif
  let clsend = match(clsline, '{')
  if clsend < 0
    let clsend = strlen(clsline)
  endif
  let clsinfo = strpart(clsline, clsbegin, clsend - clsbegin)
  let extendsbegin = match(clsinfo, ' extends')
  if extendsbegin < 0
    let implbegin = match(clsinfo, ' implements')
    if implbegin > 0
      let clsname = strpart(clsinfo, 0, implbegin)
    else
      let clsname = clsinfo
    endif
  else
    let clsname = strpart(clsinfo, 0, extendsbegin)
  endif

  let pkgtrimed = trim(pkg)
  let clsnametrimed = trim(clsname)
  let cursymbol = expand(CocAction('getCurrentFunctionSymbol'))
  if cursymbol ==# '' || cursymbol ==# clsnametrimed
    let refs = pkgtrimed.'.'.clsnametrimed
  else
    let refs = pkgtrimed.'.'.clsnametrimed.'#'.cursymbol
  endif

  let methodbegin = match(refs, '#')
  if methodbegin < 0
    echohl JEchoMsg | echo 'stop in '.refs.'.<clinit>' | echo 'stop at '.refs.':'.line('.') | echohl None
  else
    let methodsignature = strpart(refs, methodbegin + 1)
    let methodname = strpart(methodsignature, 0, match(methodsignature, '('))
    if methodname !=# ''
      echohl JEchoMsg | echo 'stop in '.strpart(refs, 0, methodbegin).'.'.methodname | echohl None
    endif
    echohl JEchoMsg | echo 'stop at '.strpart(refs, 0, methodbegin).':'.line('.') | echohl None
  endif
endfunction

function! CCEchoSignature()
  if CCCheckJavaUsage('Echo Signature') ==# 0
    return
  endif
  let hovers = CocAction('getHover')
  if len(hovers) == 0
    echohl JEchoError | echo 'Error: current symbol not found' | echohl None
    return
  endif
  echohl JEchoMsg | echo get(hovers, 0) | echohl None
endfunction

function! CCShowSnippetActions()
  if CCCheckJavaUsage('Show snippet actions') ==# 0
    return
  endif
  call popup_menu(g:cc_snippet_action_names, #{callback: 'CCSnippetActionsCallback', filter: 'CCSnippetActionsFilter', title: 'Snippet Actions', maxheight: 20})
endfunction

function! CCSnippetActionsCallback(winid, sel)
  if a:sel ==# 'x'
    return
  endif
  if a:sel < 0
    return
  endif
  call g:cc_snippet_actions[get(g:cc_snippet_action_names, a:sel - 1)]()
endfunction

function! CCSnippetActionsFilter(winid, key)
  if a:key >= '0' && a:key <= '9'
    let idx = str2nr(a:key)
    if idx ==# 0
      let idx = 10
    endif
    if idx <= len(g:cc_snippet_action_names)
      call popup_close(a:winid, idx)
      return 1
    endif
  endif
  return popup_filter_menu(a:winid, a:key)
endfunction

function! CCIterateOver()
  let hovervar = get(CocAction('getHover'), 0)
  let methodprefixbegin = match(hovervar, ' - ')
  let deletecurline = 0
  if methodprefixbegin < 0
    " method or others
    let bracketbegin = match(hovervar, '(')
    if bracketbegin < 0
      " others
      let whitespacebegin = match(hovervar, ' ')
      if whitespacebegin < 0
	echohl JEchoError | echo 'Error: not variables or methods' | echohl None
	return
      endif
      let targetvar = trim(hovervar)
      let varname = trim(get(split(targetvar, ' '), -1))
      let insert_target = 'below'
    else
      " method
      normal! F.b
      let method = strpart(hovervar, 0, bracketbegin)
      let method_name_parts = split(method, ' ')
      let methodfullname = get(method_name_parts, -1)
      let methodname = get(split(methodfullname, '\.'), -1)
      let targetvar = join(method_name_parts[:-1], ' ').' '.methodname
      exe 'normal! F.b'
      let instancename = expand('<cword>')
      let curline = trim(getline('.'))
      let instancenamebegin = match(curline, instancename)
      let rightbracketbegin = len(curline) - match(list2str(reverse(str2list(curline))), ')') - 1
      if rightbracketbegin < 0
	" multiple line: not supported
	echohl JEchoError | echo 'Error: iterate over method only support single line statement' | echohl None
	return
      endif
      let varname = strpart(curline, instancenamebegin, rightbracketbegin - instancenamebegin + 1)
      if instancenamebegin ==# 0
	let insert_target = 'inline'
	let deletecurline = 1
      else
	let insert_target = 'below'
      endif
    endif
  else
    " local variable
    let targetvar = trim(strpart(hovervar, 0, methodprefixbegin))
    let varname = trim(get(split(targetvar, ' '), -1))
    let insert_target = 'below'
    let curline = trim(getline('.'))
    if match(curline, ';') < 0
      let deletecurline = 1
    endif
  endif
  let guesstype = 'Object'
  let guessitemname = 'object'
  let importneed = 0
  if targetvar !=# ''
    let genericbegin = match(targetvar, '<')
    if genericbegin >= 0
      let genericend = len(targetvar) - match(list2str(reverse(str2list(targetvar))), '>') - 1
      let guesstype = strpart(targetvar, genericbegin + 1, genericend - genericbegin - 1)
      let guessitemname = tolower(strpart(guesstype, 0, 1)).strpart(guesstype, 1)
      let trim_item_begin = match(guessitemname, '<')
      if trim_item_begin > 0
	let guessitemname = strpart(guessitemname, 0, trim_item_begin)
      endif
      let jump_to = 'item var'
      if g:cc_snippet_auto_organize_imports ==# 1
	if CCCheckClassImported(guesstype) ==# 0
	  let importneed = 1
	endif
      endif
    endif
  endif
  if deletecurline ==# 1
    exe 'normal! dd'
    exe 'normal! O'.'for ('.guesstype.' '.guessitemname.' : '.varname.') {'
  else
    exe 'normal! o'.'for ('.guesstype.' '.guessitemname.' : '.varname.') {'
  endif
  exe 'normal! o}'
  exe 'normal! kf(f:b'
  if importneed ==# 1
    exe 'CocCommand java.action.organizeImports'
  endif
endfunction

function! _CCFillMethodParameters(only_var)
  let hovers = CocAction('getHover')
  if len(hovers) == 0
    echohl JEchoError | echo 'Error: current method signature not found' | echohl None
    return
  endif
  let hover = get(hovers, 0)
  if match(hover, ' - ') > 0
    echohl JEchoError | echo 'Error: not method: '.hover | echohl None
    return
  endif
  let bracketbegin = match(hover, '(')
  if bracketbegin < 0
    echohl JEchoError | echo 'Error: not method: '.hover | echohl None
    return
  endif
  let bracketend = match(hover, ')', bracketbegin)
  let parameters = strpart(hover, bracketbegin + 1, bracketend - bracketbegin - 1)
  if len(parameters) == 0
    echohl JEchoMsg | echo 'No parameter: '.hover | echohl None
    return
  endif
  if a:only_var ==# 1
    let param_attrs = []
    let param_vars = []
    let commaidx = 0
    let len_params = len(parameters)
    while commaidx < len_params
      let nextcommaidx = match(parameters, ',', commaidx)
      if nextcommaidx < 0
	let nextcommaidx = len_params
      endif
      let inner = strpart(parameters, commaidx, nextcommaidx - commaidx)
      let innerbracketbegin = match(inner, '<')
      if innerbracketbegin > 0
	let innerbracketend = match(parameters, '>', commaidx)
	"TODO: find next comma recursively
	let nextcommaidx = match(parameters, ',', innerbracketend)
	if nextcommaidx < 0
	  let nextcommaidx = len_params
	endif
	let whitespaceidx = match(parameters, ' ', innerbracketend)
	let var_name = strpart(parameters, whitespaceidx + 1, nextcommaidx - whitespaceidx - 1)
      else
	let var_name = trim(get(split(inner, ' '), -1))
      endif
      call add(param_vars, var_name)
      let commaidx = nextcommaidx + 1
    endwhile
    for param_attr in param_attrs
      let attr = split(param_attr, ' ')
      call add(param_vars, trim(attr[1]))
    endfor
    let parameters = join(param_vars, ', ')
  endif
  exe 'normal! f('
  exe 'normal! %a;'
  exe 'normal! hd%'
  exe 'normal! i('.parameters.')'
  exe 'normal! lx'
  exe 'normal! F(w'
endfunction

function! CCFillMethodParameters()
  call _CCFillMethodParameters(0)
endfunction

function! CCFillMethodParametersOnlyVar()
  call _CCFillMethodParameters(1)
endfunction

function! CCCheckClassImported(cls)
  let ocol = col('.')
  let oline = line('.')
  call cursor(1, 1)
  let importlineidx = search(trim(a:cls).';', 'n')
  call cursor(oline, ocol)
  let importfound = 0
  if importlineidx > 0
    let importline = getline(importlineidx)
    if match(importline, 'import ') >= 0
      let importfound = 1
    endif
  endif
  return importfound
endfunction

function! CCIntroduceLocalVariable()
  let hovers = CocAction('getHover')
  let intro_direct = 0
  if len(hovers) ==# 0
    let var_type = expand('<cword>')
    let var_name = tolower(strpart(var_type, 0, 1)).strpart(var_type, 1)
    let intro_direct = 1
  else
    let method = get(hovers, 0)
    let m_gene_begin = match(method, '<')
    if m_gene_begin < 0 || match(method, '(') > 0
      let whitespacebegin = match(method, ' ')
    else
      let whitespacebegin = match(method, ' ', match(method, '>', m_gene_begin))
    endif
    if whitespacebegin < 0
      "echohl JEchoError | echo 'Error: invalid hover: '.method | echohl None
      let var_type = get(split(method, '\.'), -1)
      let var_name = tolower(strpart(var_type, 0, 1)).strpart(var_type, 1)
      let intro_direct = 1
    endif
  endif
  if intro_direct ==# 1
    let gene_begin = match(var_name, '<')
    if gene_begin > 0
      let var_name = strpart(var_name, 0, gene_begin)
    endif
    if var_name ==# 'long'
      let var_name = 'l'
    endif
    if var_name ==# 'float'
      let var_name = 'f'
    endif
    let inserts = ' '.var_name
    let gene_found = 0
    if gene_begin > 0
      let gene_found = 1
    else
      let curline = trim(getline('.'))
      let vartypebegin = match(curline, var_type)
      if vartypebegin >= 0
	let c = strpart(curline, vartypebegin + len(var_type), 1)
	if c ==# '<'
	  let gene_found = 1
	endif
      endif
    endif
    if gene_found
      exe 'normal! f>'
    else
      let curword = expand('<cword>')
      let curline = getline('.')
      let curcol = col('.')
      let standingword = strpart(curline, curcol - len(curword), len(curword))
      if standingword !=# curword
	exe 'normal! e'
      endif
    endif
    exe 'normal! a'.inserts
    exe 'normal! F w'
    return
  endif
  let returntype = strpart(method, 0, whitespacebegin)
  if returntype ==# 'void'
    echohl JEchoError | echo 'Error: this method return type is: void' | echohl None
    return
  endif
  if strpart(returntype, 0, 1) == '<'
    "echohl JEchoError | echo 'Error: complex generic return type is not supported: '.method | echohl None
    "return
    if match(method, 'Stream.collect(') > 0
      let returntype = strpart(method, 1, match(method, '>'))
    endif
  endif

  let methodnameend = match(method, '(')
  if methodnameend < 0
    echohl JEchoError | echo 'Error: invalid method: '.method | echohl None
    return
  endif
  let methodname = strpart(method, whitespacebegin, methodnameend - whitespacebegin)
  let symbols = split(methodname, '\.')
  let onlyname = get(symbols, len(symbols) - 1)
  let cword = expand('<cword>')
  if cword !=# onlyname
    echohl JEchoError | echo 'Error: not method: '.cword | echohl None
    return
  endif

  let valname = onlyname
  if match(onlyname, 'get') == 0 && onlyname !=# 'get'
    let valname = tolower(strpart(onlyname, 3, 1)).strpart(onlyname, 4)
  endif

  let inserts = returntype.' '.valname.' = '
  let importneed = 0
  if g:cc_snippet_auto_organize_imports
    let genericbegin = match(returntype, '<')
    let importclses = []
    if genericbegin < 0
      add(importclses, returntype)
    else
      let genericend = match(returntype, '>')
      let cls = strpart(returntype, 0, genericbegin)
      call add(importclses, cls)
      let genericcls = strpart(returntype, genericbegin + 1, genericend - genericbegin - 1)
      call add(importclses, genericcls)
    endif
    if len(importclses) > 0
      for cls in importclses
	if CCCheckClassImported(cls) ==# 0
	  let importneed = 1
	  break
	endif
      endfor
    endif
  endif
  let ocol = col('.')
  let oline = line('.')
  call cursor(oline, 1)
  let l:foundline = search('\.'.onlyname, 'n')
  call cursor(oline, ocol)
  normal! F.b
  exe 'normal! I'.inserts
  normal! bb
  if importneed ==# 1
    exe 'CocCommand java.action.organizeImports'
  endif
endfunction

function! CCCheckJavaUsage(action)
  let filename = tolower(expand('%h'))
  let extbegin = match(filename, '\.java')
  if extbegin > 0 && (extbegin + 5) ==# len(filename)
    return 1
  endif
  if match(filename, 'jdt://') ==# 0
    return 1
  endif
  echohl JEchoError | echo 'Error: ['.a:action.'] is only supported in java source file' | echohl None
  return 0
endfunction

function! CCGotoImplemention()
  if CCCheckJavaUsage('Goto implemention') ==# 0
    return
  endif
  call CocAction('jumpImplementation')
endfunction

function! CCGotoImplementionTabPage()
  if CCCheckJavaUsage('Open new tab page and goto implemention') ==# 0
    return
  endif
  call CocAction('jumpImplementation', 'tabe')
endfunction

function! CCGotoDefinition()
  if CCCheckJavaUsage('Goto definition') ==# 0
    return
  endif
  call CocAction('jumpDefinition')
  call CocActionAsync('highlight')
endfunction

function! CCGotoDefinitionTabPage()
  if CCCheckJavaUsage('Open new tab page and goto definition') ==# 0
    return
  endif
  call CocAction('jumpDefinition', 'tabe')
endfunction

function! CCGotoReferences()
  if CCCheckJavaUsage('Goto references') ==# 0
    return
  endif
  call CocAction('jumpReferences')
endfunction

function! CCGotoCurrentMethodDefinition()
  if CCCheckJavaUsage('Goto current method definition') ==# 0
    return
  endif
  let cursymbol = expand(CocAction('getCurrentFunctionSymbol'))
  if cursymbol ==# 'v:null'
    echohl JEchoError | echo "Error: get symbol failed, please try again later" | echohl None
    return
  endif
  let methodend = match(cursymbol, '(')
  if cursymbol ==# '' || methodend < 0
    return
  endif
  let methodname = strpart(cursymbol, 0, methodend)
  let keyword = ' '.methodname.'('
  " mark jump
  exe 'normal! m`'
  let sch_rst = search(keyword, 'b')
  if sch_rst ==# 0
    echohl JEchoError | echo "Error: can\'t find method: ".methodname | echohl None
    return
  endif
  let tl = getline(sch_rst)
  normal! w
  call CocActionAsync('highlight')
endfunction

function! CCHighlightSymbol()
  if CCCheckJavaUsage('Highlight symbol') ==# 0
    return
  endif
  call CocActionAsync('highlight')
endfunction
" >> coc functions


" << navigate functions
function! CCNavigate(keywords)
  let lower_keywords = tolower(a:keywords)
  if lower_keywords ==# a:keywords
    let additional_options = '--glob-case-insensitive'
  else
    let additional_options = ''
  endif

  let g:cc_navigate_result_files = []
  let g:cc_navigate_keywords = 'None'
  let g:cc_navigate_select_idx = -1
  let res = trim(system("rg ".additional_options." --files -g ".a:keywords."*"))
  let g:cc_navigate_result_files = split(res, '\n')
  if len(g:cc_navigate_result_files) > 0
    let g:cc_navigate_keywords = a:keywords
    let g:cc_navigate_result_files = sort(g:cc_navigate_result_files)
    for i in range(len(g:cc_navigate_result_files))
      let g:cc_navigate_result_files[i] = (i + 1).'. '.g:cc_navigate_result_files[i]
    endfor
    call CCOpenNavigateSelector()
  else
    echohl ErrorMsg | echo 'Keywords not found: '.a:keywords | echohl None
  endif
endfunction

function! CCOpenNavigateSelector()
  if len(g:cc_navigate_result_files) > 0
    if g:cc_navigate_select_idx < 0
      call popup_menu(g:cc_navigate_result_files, #{callback: 'CCNavigateMenuCallback', filter: 'CCNavigateMenuFilter', title: '<'.getcwd().'>: '.g:cc_navigate_keywords, maxheight: 20})
    else
      call popup_menu(g:cc_navigate_result_files, #{callback: 'CCNavigateMenuCallback', filter: 'CCNavigateMenuFilter', title: '<'.getcwd().'>: '.g:cc_navigate_keywords, maxheight: 20, firstline: g:cc_navigate_select_idx})
    endif
  else
    echohl ErrorMsg | echo 'Usage: Navigate <keywords>' | echohl None
  endif
endfunction

function! CCNavigateMenuCallback(id, sel)
  if a:sel ==# 'x'
    return
  endif
  if a:sel < 0
    return
  endif
  let item = get(g:cc_navigate_result_files, a:sel - 1)
  let idx_begin = match(item, '\. ')
  let g:cc_navigate_select_idx = str2nr(strpart(item, 0, idx_begin))
  let path = strpart(item, idx_begin + 2)
  exe 'tabf '.path
endfunction

function! CCNavigateMenuFilter(winid, key)
  let numberofresults = len(g:cc_navigate_result_files)
  if a:key >= '0' && a:key <= '9'
    let idx = str2nr(a:key)
    if idx ==# 0
      let idx = 10
    endif
    if idx <= numberofresults
      call popup_close(a:winid, idx)
      return 1
    endif
  endif
  if a:key ==# 'g'
    call popup_setoptions(a:winid, #{firstline: 1})
    return 1
  endif
  if a:key ==# 'G'
    let numlines = line('$', a:winid)
    call popup_setoptions(a:winid, #{firstline: numlines})
    return 1
  endif
  "if a:key ==# "\<c-f>"
  "  let pos = popup_getpos(a:winid)
  "  echo pos
  "endif
  return popup_filter_menu(a:winid, a:key)
endfunction
" >> navigate functions


" << TabSelector functions
function! CCTabSelector()
  let tabnamelist = []
  for i in range(tabpagenr('$'))
    let idx = i + 1
    let name = bufname(tabpagebuflist(idx)[0])
    let maxwords = 80 - 4
    if len(name) > maxwords
      let name = strpart(name, len(name) - maxwords)
    endif
    call add(tabnamelist, idx.'. '.name)
  endfor
  if g:cc_tab_selector_last_idx ==# -1
    call popup_menu(tabnamelist, #{callback: 'CCTabSelectorMenuCallback', filter: 'CCTabSelectorMenuFilter', title: 'Tabs', maxheight: 20, maxwidth: 80, minwidth: 80, firstline: tabpagenr()})
  else
    call popup_menu(tabnamelist, #{callback: 'CCTabSelectorMenuCallback', filter: 'CCTabSelectorMenuFilter', title: 'Tabs', maxheight: 20, maxwidth: 80, minwidth: 80, firstline: g:cc_tab_selector_last_idx})
  endif
endfunction!

function! CCTabSelectorMenuCallback(id, sel)
  if a:sel ==# 'x'
    return
  endif
  let g:cc_tab_selector_last_idx = -1
  if a:sel < 0
    return
  endif
  exe 'tabn'.a:sel
endfunction

function! CCTabSelectorMenuFilter(winid, key)
  let numoftabs = tabpagenr('$')
  if a:key >= '0' && a:key <= '9'
    let idx = str2nr(a:key)
    if idx ==# 0
      let idx = 10
    endif
    if idx <= numoftabs
      call popup_close(a:winid, idx)
      return 1
    endif
  endif
  if a:key ==# 'g'
      call popup_close(a:winid, 'x')
      let g:cc_tab_selector_last_idx = 1
      call CCTabSelector()
      return 1
  endif
  if a:key ==# 'G'
      call popup_close(a:winid, 'x')
      let g:cc_tab_selector_last_idx = numoftabs
      call CCTabSelector()
      return 1
  endif
  return popup_filter_menu(a:winid, a:key)
endfunction
" >> TabSelector functions


" << BufferSelector functions
function! CCGetBufferList()
  let bufnamelist = []
  let list_idx = 0
  for i in range(bufnr('$'))
    let idx = i + 1
    if buflisted(idx)
      let name = bufname(idx)
      let maxwords = 80 - 7
      if len(name) > maxwords
	let name = strpart(name, len(name) - maxwords)
      endif
      call add(bufnamelist, (list_idx + 1).'. ['.idx.'] '.name)
      let list_idx = list_idx + 1
    endif
  endfor
  return bufnamelist
endfunction

function! CCBufferSelector()
  let bufnamelist = CCGetBufferList()
  if g:cc_buffer_selector_last_idx ==# -1
    call popup_menu(bufnamelist, #{callback: 'CCBufferSelectorMenuCallback', filter: 'CCBufferSelectorMenuFilter', title: 'Buffers', maxheight: 20, maxwidth: 80, minwidth: 80})
  else
    call popup_menu(bufnamelist, #{callback: 'CCBufferSelectorMenuCallback', filter: 'CCBufferSelectorMenuFilter', title: 'Buffers', maxheight: 20, maxwidth: 80, minwidth: 80, firstline: g:cc_buffer_selector_last_idx})
  endif
endfunction!

function! CCBufferSelectorMenuCallback(id, sel)
  if a:sel ==# 'x'
    return
  endif
  let g:cc_buffer_selector_last_idx = -1
  if a:sel < 0
    return
  endif
  let bufnamelist = CCGetBufferList()
  let target = get(bufnamelist, a:sel - 1)
  let idx_begin = match(target, '\. [')
  let idx_begin = idx_begin + 3
  let idx_end = match(target, ']', idx_begin) 
  let idx = strpart(target, idx_begin, idx_end - idx_begin)
  exe 'b'.idx
endfunction

function! CCBufferSelectorMenuFilter(winid, key)
  let numofbuffers = len(CCGetBufferList())
  if a:key >= '0' && a:key <= '9'
    let idx = str2nr(a:key)
    if idx ==# 0
      let idx = 10
    endif
    if idx <= numofbuffers
      call popup_close(a:winid, idx)
      return 1
    endif
  endif
  if a:key ==# 'g'
      call popup_close(a:winid, 'x')
      let g:cc_buffer_selector_last_idx = 1
      call CCBufferSelector()
      return 1
  endif
  if a:key ==# 'G'
      call popup_close(a:winid, 'x')
      let g:cc_buffer_selector_last_idx = numofbuffers
      call CCBufferSelector()
      return 1
  endif
  return popup_filter_menu(a:winid, a:key)
endfunction
" >> BufferSelector functions

function! CCInitJavaFile()
  let filepath = expand('%')
  let pathcomps = split(filepath, '/')
  let filename = get(pathcomps, -1)
  let filename_no_ext = get(split(filename, '\.'), 0)
  let src_path_idx = match(filepath, 'src/main/java/')
  if src_path_idx < 0
    let src_path_idx = match(filepath, 'src/test/java')
  endif
  let pkg_begin = src_path_idx + 14
  let package = substitute(strpart(filepath, pkg_begin, len(filepath) - len(filename) - pkg_begin - 1), '/', '.', 'g')
  exe 'normal! ggI'.'package '.package.';'
  exe 'normal! o'
  exe 'normal! opublic class '.filename_no_ext.' {'
  exe 'normal! o}'
  exe 'normal! k'
  exe 'w'
  exe 'e!'
endfunction

function! CCCreateJavaClass(class_name)
  let cls_name = substitute(trim(a:class_name), ' ', '', 'g')
  exe 'tabe %:p:h/'.toupper(strpart(cls_name, 0, 1)).strpart(cls_name, 1).'.java'
  exe 'w'
  exe 'call CCInitJavaFile()'
endfunction

function! CCOrganizeImports()
  exe 'CocCommand java.action.organizeImports'
endfunction

function! CCOpenServerLog()
  exe 'CocCommand java.open.serverLog'
endfunction

function! CCOpenVerboseServerLog()
  echohl JEchoMsg | echo 'Verbose log should be enbaled by setting(CocConfig): "java.trace.server": "verbose"' | echohl None
  exe 'CocCommand workspace.showOutput java'
endfunction

function! CCCleanWorkspace()
  exe 'CocCommand java.clean.workspace'
endfunction

function! CCOpenDebugger()
  exe 'CocCommand java.debug.vimspector.start'
endfunction

function! CCIndexedTabLine()
  let s = ''
  for i in range(tabpagenr('$'))
    " select the highlighting
    if i + 1 == tabpagenr()
      let s .= '%#TabLineSel#'
    else
      let s .= '%#TabLine#'
    endif

    " set the tab page number (for mouse clicks)
    let s .= '%' . (i + 1) . 'T'

    " the label is made by MyTabLabel()
    let s .= ' %{CCIndexedTabLabel(' . (i + 1) . ')} '
  endfor

  " after the last tab fill with TabLineFill and reset tab page nr
  let s .= '%#TabLineFill#%T'

  " right-align the label to close the current tab page
  if tabpagenr('$') > 1
    let s .= '%=%#TabLine#%999 '
  endif

  return s
endfunction

function! CCIndexedTabLabel(n)
  let buflist = tabpagebuflist(a:n)
  let winnr = tabpagewinnr(a:n)
  let filepath = bufname(buflist[winnr - 1])
  if filepath ==# ''
    let filename = '[No Name]'
  else
    let filename = get(split(filepath, '/'), -1)
  endif
  let changed = getbufinfo(a:n)[0].changed
  if changed
    let prefix = '+ '
  else
    let prefix = ''
  endif
  let idx = a:n.'.'
  let idx = ''
  return prefix.idx.filename
endfunction

function! CCJavaAdditionalHighlight()
  syn keyword javaType Long String Integer Float Boolean Object
  syn match javaStatementEnd ";"
  "hi def link javaStatementEnd Statement
endfunction

" << Others Tools
function! JSONFormat()
  execute '%!python -c "import json,sys,collections,re; sys.stdout.write(re.sub(r\"\\\u[0-9a-f]{4}\", lambda m:m.group().decode(\"unicode_escape\").encode(\"utf-8\"),json.dumps(json.load(sys.stdin, object_pairs_hook=collections.OrderedDict), indent=2)))"'
  exe 'set filetype=json'
endfunction
command! JSONFormat :call JSONFormat()
" >> Others Tools
